package com.ztesoft.book.common.utils.wechat;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ztesoft.book.common.constant.CommonParamUtil;
import com.ztesoft.book.common.constant.Constant;
import com.ztesoft.book.common.utils.Result;
import com.ztesoft.book.common.utils.jwt.JwtUtil;
import com.ztesoft.book.common.utils.redis.RedisUtils;
import com.ztesoft.book.common.utils.wechat.qo.UrlLinkQO;
import com.ztesoft.book.common.utils.wechat.util.HttpRequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import org.springframework.stereotype.Component;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description 微信相关工具类
 * @Author YangFan
 * @Date 2021-07-19 10:40
 */
@Slf4j
@Component
public final class WechatAccessToken {

    private WechatAccessToken() {
    }

    /**
     * 根据小程序登录CODE获取OPEN_ID接口 每个用户每分钟100次
     */
    public static final String jscode2session_url = "https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code";

    /**
     * 获取小程序全局唯一后台接口调用凭据（access_token）
     */
    public static final String getAccessToken_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";

    /**
     * 获取小程序 URL Link
     */
    public static final String getUrlLink_url = "https://api.weixin.qq.com/wxa/generate_urllink?access_token=ACCESS_TOKEN";


    /**
     * 方法功能描述:
     * < 通过临时CODE 获取 小程序OPENID >
     *
     * @param jsCode 【参数 1 小程序前端登录获取code】
     * @param appId  【参数 2 appid】
     * @param secret 【参数 3 secret】
     * @return String                        【返回值 注释说明】
     * @author YangFan
     * @date 2021-07-19 10:20
     */
    public static Result getOpenIdByJscode(String jsCode, String appId, String secret) {
        String requestUrl = jscode2session_url;

        requestUrl = requestUrl.replace("APPID", appId);
        requestUrl = requestUrl.replace("SECRET", secret);
        requestUrl = requestUrl.replace("JSCODE", jsCode);

        JSONObject jsonObject = HttpRequestUtil.hutoolHttpRequest(requestUrl, "GET", null);
        Object errCode = jsonObject.get("errcode");
        if (errCode == null) {
            String openId = jsonObject.getString("openid");
            String session_key = jsonObject.getString("session_key");

            String currentTimeMillis = String.valueOf(System.currentTimeMillis());
            String token = JwtUtil.signUserExt(openId, currentTimeMillis);
            Map<String, Object> okmap = new HashMap(16);
            okmap.put("openId", openId);
            okmap.put("sessionKey", session_key);
            okmap.put("token", token);
            return Result.successNoMap(okmap);
        }
        else {
            Map<String, Object> errmap = new HashMap(16);
            String errMsg = jsonObject.getString("errmsg");
            errmap.put("errCode", errCode);
            errmap.put("errMsg", errMsg);
            return Result.errorNoMap(CommonParamUtil.INT_F_1, errmap);
        }
    }


    /**
     * 方法功能描述:
     * < 微信小程序获取unionId的解密方法 >
     *
     * @param encryptedData 【参数 1 注释说明】
     * @param iv            【参数 2 注释说明】
     * @param sessionKey    【参数 3 注释说明】
     * @return Map<String, Object>
     * @author YangFan
     * @date 2021-07-20 15:51
     */
    public static Map<String, Object> decryptUserInfo(String encryptedData, String iv, String sessionKey) {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        byte[] dataStr = Base64.decode(encryptedData);
        byte[] keyBytes = Base64.decode(sessionKey);
        int base = 16;
        if (keyBytes.length % base != 0) {
            int groups = keyBytes.length / base + (keyBytes.length % base != 0 ? 1 : 0);
            byte[] temp = new byte[groups * base];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(keyBytes, 0, temp, 0, keyBytes.length);
            keyBytes = temp;
        }

        Security.addProvider(new BouncyCastleProvider());
        Map<String, Object> map = null;
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyBytes, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(Base64.decode(iv)));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
            byte[] resultByte = cipher.doFinal(dataStr);
            String json = new String(resultByte, "UTF-8");
            map = JSON.parseObject(json, Map.class);
        }
        catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException
                | InvalidParameterSpecException | InvalidKeyException | InvalidAlgorithmParameterException
                | IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException e) {
            log.error("decryptUserInfo", e);
        }
        return map;
    }


    /**
     * 方法功能描述:
     * < 获取小程序全局token，并缓存 >
     *
     * @param appId  【参数 1 注释说明】
     * @param secret 【参数 2 注释说明】
     * @return Result                        【返回值 注释说明】
     * @author YangFan
     * @date 2021-07-19 13:50
     */
    public static Result getAccessToken(String appId, String secret) {
        String redis_token = RedisUtils.get(Constant.PREFIX_WX_XCX_TOKEN + appId);
        if (!StringUtils.isBlank(redis_token)) {
            Map<String, Object> okmap = new HashMap(16);
            okmap.put("access_token", redis_token);
            return Result.successNoMap(okmap);
        }

        String requestUrl = getAccessToken_url;

        requestUrl = requestUrl.replace("APPID", appId);
        requestUrl = requestUrl.replace("APPSECRET", secret);

        JSONObject jsonObject = HttpRequestUtil.hutoolHttpRequest(requestUrl, "GET", null);
        Object errCode = jsonObject.get("errcode");
        if (errCode == null) {
            String access_token = jsonObject.getString("access_token");

            // *** 一定要注意token被别处刷新的风险，使用的业务中如果发现token失效报错，需要重新调用
            // 存入redis
            RedisUtils.set(Constant.PREFIX_WX_XCX_TOKEN + appId, access_token, 7000);

            Map<String, Object> okmap = new HashMap(16);
            okmap.put("access_token", access_token);
            return Result.successNoMap(okmap);
        }
        else {
            Map<String, Object> errmap = new HashMap(16);
            String errMsg = jsonObject.getString("errmsg");
            errmap.put("errCode", errCode);
            errmap.put("errMsg", errMsg);
            return Result.errorNoMap(CommonParamUtil.INT_F_1, errmap);
        }
    }


    /**
     * 方法功能描述:
     * < 获取小程序 URL Link >
     *
     * @param param 【参数 1 注释说明】
     * @return Result                        【返回值 注释说明】
     * @author YangFan
     * @date 2021-07-19 15:10
     */
    public static Result getUrlLink(UrlLinkQO param, String appId, String secret) {
        String requestUrl = getUrlLink_url;

        requestUrl = requestUrl.replace("ACCESS_TOKEN", param.getAccess_token());
        HashMap map = new HashMap(16);
        map.put("path", param.getPath());
        map.put("query", param.getQuery());
        if (param.getIs_expire() == 0) {
            map.put("is_expire", true);
        }
        else {
            map.put("is_expire", false);
        }
        map.put("expire_type", param.getExpire_type());
        map.put("expire_interval", param.getExpire_interval());

        JSONObject jsonObject = HttpRequestUtil.httpRequest(requestUrl, "POST", JSON.toJSONString(map));
        String errCode = jsonObject.getString("errcode");
        if (CommonParamUtil.STRING_0.equals(errCode)) {
            return Result.successNoMap(jsonObject);
        }
        else if (CommonParamUtil.STRING_40001.equals(errCode)) {
            // token在别处调用导致提前失效
            String tokenUrl = getAccessToken_url;
            tokenUrl = tokenUrl.replace("APPID", appId);
            tokenUrl = tokenUrl.replace("APPSECRET", secret);
            JSONObject tokenObject = HttpRequestUtil.hutoolHttpRequest(tokenUrl, "GET", null);
            Object tokenErrCode = tokenObject.get("errcode");
            if (tokenErrCode == null) {
                String access_token = tokenObject.getString("access_token");
                RedisUtils.set(Constant.PREFIX_WX_XCX_TOKEN + appId, access_token, 7000);
                // 用新token重新获取url
                param.setAccess_token(access_token);
                return getUrlLinkAgain(param);
            }
            else {
                Map<String, Object> errmap = new HashMap(16);
                String errMsg = tokenObject.getString("errmsg");
                errmap.put("errCode", errCode);
                errmap.put("errMsg", errMsg);
                return Result.errorNoMap(CommonParamUtil.INT_F_1, errmap);
            }
        }
        else {
            Map<String, Object> errmap = new HashMap(16);
            String errMsg = jsonObject.getString("errmsg");
            errmap.put("errCode", errCode);
            errmap.put("errMsg", errMsg);
            return Result.errorNoMap(CommonParamUtil.INT_F_1, errmap);
        }
    }


    public static Result getUrlLinkAgain(UrlLinkQO param) {
        String requestUrl = getUrlLink_url;
        requestUrl = requestUrl.replace("ACCESS_TOKEN", param.getAccess_token());
        HashMap map = new HashMap(16);
        map.put("path", param.getPath());
        map.put("query", param.getQuery());
        if (param.getIs_expire() == 0) {
            map.put("is_expire", true);
        }
        else {
            map.put("is_expire", false);
        }
        map.put("expire_type", param.getExpire_type());
        map.put("expire_interval", param.getExpire_interval());

        JSONObject jsonObject = HttpRequestUtil.httpRequest(requestUrl, "POST", JSON.toJSONString(map));
        String errCode = jsonObject.getString("errcode");
        if (CommonParamUtil.STRING_0.equals(errCode)) {
            return Result.successNoMap(jsonObject);
        }
        else {
            Map<String, Object> errmap = new HashMap(16);
            String errMsg = jsonObject.getString("errmsg");
            errmap.put("errCode", errCode);
            errmap.put("errMsg", errMsg);
            return Result.errorNoMap(CommonParamUtil.INT_F_1, errmap);
        }
    }


}